package storage

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"
)

const logSizeThreshold = 1024 * 1024 // 1 MIB

type fileLock interface {
	release() error
}
type fileStotageLock struct {
	fs *fileStorage
}

type fileStorage struct {
	path     string
	readOnly bool
	mu       sync.Mutex
	flock    fileLock
	slock    *fileStotageLock
	logw     *os.File
	logSize  int64
	buf      []byte
	open     int
	day      int
}

func (fs *fileStorage) printDay(time time.Time) error {
	if fs.day == time.Day() {
		return nil
	}
	fs.day = time.Day()
	_, err := fs.logw.Write([]byte("=============" + time.Format("Jan 2, 2006 (MST)") + "==============\n"))
	return err
}

func itoa(buf []byte, i int, wid int) []byte {
	u := uint(i)
	if u == 0 && wid <= 1 {
		return append(buf, '0')
	}

	var b [32]byte
	bp := len(b)

	for ; u > 0 || wid > 0; u /= 10 {
		bp--
		wid--
		b[bp] = byte(u%10) + '0'
	}
	return append(buf, b[bp:]...)
}
func (fs *fileStorage) doLog(time time.Time, str string) {
	if fs.logSize > logSizeThreshold {
		fs.logw.Close()
		fs.logw = nil
		fs.logSize = 0
		if err := rename(filepath.Join(fs.path, "LOG"), filepath.Join(fs.path, "LOG.old")); err != nil {
			return
		}
	}
	if fs.logw == nil {
		var err error
		fs.logw, err = os.OpenFile(filepath.Join(fs.path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			return
		}
		fs.day = 0
	}
	if err := fs.printDay(time); err != nil {
		return
	}

	hour, min, sec := time.Clock()
	msec := time.Nanosecond() / 1e3

	// time
	fs.buf = itoa(fs.buf[:0], hour, 2)
	fs.buf = append(fs.buf, ':')
	fs.buf = itoa(fs.buf, min, 2)
	fs.buf = append(fs.buf, ':')
	fs.buf = itoa(fs.buf, sec, 2)
	fs.buf = append(fs.buf, '.')
	fs.buf = itoa(fs.buf, msec, 6)
	fs.buf = append(fs.buf, ' ')

	//write
	fs.buf = append(fs.buf, []byte(str)...)
	fs.buf = append(fs.buf, '\n')
	n, _ := fs.logw.Write(fs.buf)
	fs.logSize += int64(n)
}

func (fs *fileStorage) log(str string) {
	if !fs.readOnly {
		fs.doLog(time.Now(), str)
	}
}
func (fs *fileStorage) Close() error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	if fs.open < 0 {
		return ErrClosed
	}
	runtime.SetFinalizer(fs, nil)

	if fs.open > 0 {
		fs.log(fmt.Sprintf("close: warning, %d file still open", fs.open))
	}
	fs.open = -1
	if fs.logw != nil {
		fs.logw.Close()
	}
	return fs.flock.release()
}

func OpenFile(path string, readOnly bool) (Storage, error) {
	if fi, err := os.Stat(path); err != nil {
		if !fi.IsDir() {
			return nil, fmt.Errorf("storage: open %s : not a directory", path)
		}
	} else if os.IsNotExist(err) && !readOnly {
		if err := os.MkdirAll(path, 0755); err != nil {
			return nil, err
		}
	} else {
		return nil, err
	}

	flock, err := newFileLock(filepath.Join(path, "LOCK"), readOnly)

	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			if ferr := flock.release(); ferr != nil {
				err = fmt.Errorf("error opening file (%v);error unlocking file(%v)", err, ferr)
			}
		}
	}()

	var (
		logw    *os.File
		logSize int64
	)

	if !readOnly {
		logw, err = os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
		if err != nil {
			return nil, err
		}
		logSize, err = logw.Seek(0, os.SEEK_END)
		if err != nil {
			logw.Close()
			return nil, err
		}
	}

	fs := &fileStorage{
		path:     path,
		readOnly: readOnly,
		flock:    flock,
		logw:     logw,
		logSize:  logSize,
	}
	runtime.SetFinalizer(fs, (*fileStorage).Close)
	return fs, nil

}
